package com.haohan.cloud.scm.bill.core.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.haohan.cloud.scm.api.bill.dto.BillInfoDTO;
import com.haohan.cloud.scm.api.bill.dto.SettlementDTO;
import com.haohan.cloud.scm.api.bill.entity.BaseBill;
import com.haohan.cloud.scm.api.bill.entity.SettlementAccount;
import com.haohan.cloud.scm.api.bill.req.SettlementReq;
import com.haohan.cloud.scm.api.bill.req.SummarySettlementReq;
import com.haohan.cloud.scm.api.bill.trans.ScmBillTrans;
import com.haohan.cloud.scm.api.bill.trans.ScmSettlementTrans;
import com.haohan.cloud.scm.api.bill.vo.BillInfoVO;
import com.haohan.cloud.scm.api.bill.vo.SettlementInfoVO;
import com.haohan.cloud.scm.api.constant.NumberPrefixConstant;
import com.haohan.cloud.scm.api.constant.ScmCacheNameConstant;
import com.haohan.cloud.scm.api.constant.enums.bill.SettlementStyleEnum;
import com.haohan.cloud.scm.api.constant.enums.common.ReviewStatusEnum;
import com.haohan.cloud.scm.api.constant.enums.common.SettlementTypeEnum;
import com.haohan.cloud.scm.api.constant.enums.crm.PayStatusEnum;
import com.haohan.cloud.scm.api.constant.enums.manage.PhotoTypeEnum;
import com.haohan.cloud.scm.api.constant.enums.opc.YesNoEnum;
import com.haohan.cloud.scm.api.constant.enums.purchase.PayTypeEnum;
import com.haohan.cloud.scm.api.manage.dto.PhotoGroupDTO;
import com.haohan.cloud.scm.api.manage.trans.PhotoTrans;
import com.haohan.cloud.scm.bill.core.BillServiceFactory;
import com.haohan.cloud.scm.bill.core.SettlementCoreService;
import com.haohan.cloud.scm.bill.service.SettlementAccountService;
import com.haohan.cloud.scm.bill.utils.ScmBillUtils;
import com.haohan.cloud.scm.common.tools.exception.ErrorDataException;
import com.haohan.cloud.scm.common.tools.util.ScmIncrementUtil;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author dy
 * @date 2019/12/6
 */
@Service
@AllArgsConstructor
@Slf4j
public class SettlementCoreServiceImpl implements SettlementCoreService {
    private final SettlementAccountService settlementAccountService;
    private final ScmIncrementUtil scmIncrementUtil;
    private final ScmBillUtils scmBillUtils;

    @Override
    public SettlementInfoVO fetchInfo(String settlementSn) {
        SettlementAccount account = settlementAccountService.fetchBySn(settlementSn);
        if (null == account) {
            throw new ErrorDataException("结算单有误");
        }
        SettlementInfoVO result = new SettlementInfoVO(account);
        // 图片组
        result.setPhotoList(scmBillUtils.fetchPhotoList(account.getGroupNum()));
        // 查询 是否存在随机码
        String randomCode = queryRandomCode(settlementSn);
        boolean flag = StrUtil.isEmpty(randomCode);
        result.setSettleFlag(flag);
        result.setRandomCode(randomCode);
        return result;
    }

    @Override
    public IPage<SettlementInfoVO> findPage(Page<SettlementAccount> page, SettlementReq req) {
        SettlementAccount account = req.transTo();
        // 非eq处理
        LocalDate start = req.getStartDate();
        LocalDate end = req.getEndDate();
        boolean flag = null != start && null != end;

        IPage<SettlementAccount> accountPage = settlementAccountService.page(page, Wrappers.query(account).lambda()
                .like(StrUtil.isNotEmpty(req.getPmName()), SettlementAccount::getPmName, req.getPmName())
                .like(StrUtil.isNotEmpty(req.getCompanyName()), SettlementAccount::getCompanyName, req.getCompanyName())
                .like(StrUtil.isNotEmpty(req.getCompanyOperator()), SettlementAccount::getCompanyOperator, req.getCompanyOperator())
                .like(StrUtil.isNotEmpty(req.getSettlementDesc()), SettlementAccount::getSettlementDesc, req.getSettlementDesc())
                .like(StrUtil.isNotEmpty(req.getOperatorName()), SettlementAccount::getOperatorName, req.getOperatorName())
                .apply(flag, "DATE(settlement_time) between {0} and {1}", start, end)
                .in(CollUtil.isNotEmpty(req.getCompanyIdSet()), SettlementAccount::getCompanyId, req.getCompanyIdSet())
                .orderByDesc(SettlementAccount::getCreateDate)
        );
        IPage<SettlementInfoVO> result = new Page<>(accountPage.getCurrent(), accountPage.getSize(), accountPage.getTotal());
        if (result.getTotal() > 0) {
            result.setRecords(accountPage.getRecords().stream()
                    .map(SettlementInfoVO::new)
                    .collect(Collectors.toList()));
        }
        return result;
    }

    /**
     * 是否汇总结算 设置汇总结算编号、状态
     *
     * @param settlementSn
     * @param billInfo
     */
    @Override
    public void checkSettlementSummary(String settlementSn, BillInfoVO billInfo) {
        YesNoEnum settlementSummaryFlag = YesNoEnum.no;
        if (StrUtil.isNotEmpty(settlementSn)) {
            SettlementAccount settlement = settlementAccountService.fetchBySn(settlementSn);
            // 结算单类型 为 明细
            if (null != settlement && settlement.getSettlementStyle() != SettlementStyleEnum.normal) {
                settlementSummaryFlag = YesNoEnum.yes;
                billInfo.setSummarySettlementSn(settlement.getSummarySettlementSn());
            }
        }
        billInfo.setSettlementSummaryFlag(settlementSummaryFlag);
    }


    /**
     * 创建普通结算单，已存在则修改 (结算单类型不变，普通或明细)
     * 会修改账单状态(审核通过)、记录结算单号
     *
     * @param bill id/billSn/billType
     * @param <T>
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public <T extends BaseBill> SettlementAccount createNormalSettlement(T bill) {
        SettlementAccount account = settlementAccountService.fetchByBillSn(bill.getBillSn());
        if (null == account) {
            account = ScmBillTrans.initSettlement(bill);
            settlementAccountService.save(account);
        } else {
            // 修改金额
            if (account.getSettlementStatus() == YesNoEnum.yes) {
                throw new ErrorDataException("已结算账单不可修改金额");
            }
            if (account.getSettlementAmount().compareTo(bill.getBillAmount()) != 0) {
                SettlementAccount update = new SettlementAccount();
                update.setId(account.getId());
                update.setSettlementAmount(bill.getBillAmount());
                settlementAccountService.updateById(update);
            }
        }

        // 修改账单
        BillInfoDTO params = new BillInfoDTO();
        params.setId(bill.getId());
        params.setReviewStatus(ReviewStatusEnum.success);
        params.setSettlementSn(account.getSettlementSn());
        params.setRemarks(bill.getRemarks());
        BillServiceFactory.getBillService(account.getSettlementType()).updateBillById(params);
        return account;
    }

    /**
     * 多个账单(已审核)
     * 合并结算, 创建汇总结算单
     *
     * @param req
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SettlementAccount createSummarySettlement(SummarySettlementReq req) {
        int size = req.getBillSnList().size();
        if (size < 2 || size > 10) {
            throw new ErrorDataException("需选择多个账单合并结算(不超过10个)");
        }
        List<SettlementAccount> settlementList = new ArrayList<>(size);
        Set<String> companyIdSet = new HashSet<>(8);
        req.getBillSnList().forEach(billSn -> {
            SettlementAccount settlement = settlementAccountService.fetchByBillSn(billSn);
            // 判断单个账单, 是否已有(普通)结算单,且未结算,不在待支付状态,
            if (null == settlement || settlement.getSettlementStyle() != SettlementStyleEnum.normal
                    || YesNoEnum.no != settlement.getSettlementStatus() || null != queryRandomCode(settlement.getSettlementSn())) {
                throw new ErrorDataException("账单状态有误, 不可合并结算：" + billSn);
            }
            companyIdSet.add(settlement.getCompanyId());
            settlementList.add(settlement);
        });
        if (companyIdSet.size() > 1) {
            throw new ErrorDataException("账单所属公司不同, 不可合并结算");
        }
        // 单个账单对应结算单合并处理
        String summarySn = scmIncrementUtil.inrcSnByClass(SettlementAccount.class, NumberPrefixConstant.SETTLEMENT_ACCOUNT_SN_PRE);
        // 总金额， 应收为正数
        final BigDecimal[] total = {BigDecimal.ZERO};
        settlementList.forEach(settlement -> {
            if (settlement.getSettlementType() == SettlementTypeEnum.receivable) {
                total[0] = total[0].add(settlement.getSettlementAmount());
            } else {
                total[0] = total[0].subtract(settlement.getSettlementAmount());
            }
            SettlementAccount update = new SettlementAccount();
            update.setId(settlement.getId());
            update.setSettlementStyle(SettlementStyleEnum.detail);
            update.setSummarySettlementSn(summarySn);
            settlementAccountService.updateById(update);
        });
        SettlementAccount summary = ScmBillTrans.initSummarySettlement(settlementList.get(0));
        summary.setSettlementSn(summarySn);
        summary.setRemarks(settlementList.stream()
                .map(SettlementAccount::getBillSn)
                .collect(Collectors.joining(",", "|合并的账单:", "|")));
        if (total[0].compareTo(BigDecimal.ZERO) < 0) {
            summary.setSettlementType(SettlementTypeEnum.payable);
            summary.setSettlementAmount(total[0].abs());
        } else {
            summary.setSettlementType(SettlementTypeEnum.receivable);
            summary.setSettlementAmount(total[0]);
        }
        settlementAccountService.save(summary);
        return summary;
    }

    /**
     * 准备结算 返回随机码用于验证
     *
     * @param settlementSn
     * @param amount       结算金额
     * @return
     */
    @Override
    public SettlementInfoVO readySettlement(String settlementSn, BigDecimal amount) {
        SettlementAccount account = settlementAccountService.fetchBySn(settlementSn);
        // 明细结算单不可单独结算
        if (null == account || account.getSettlementStyle() == SettlementStyleEnum.detail) {
            throw new ErrorDataException("结算单有误");
        }
        if (account.getSettlementStatus() == YesNoEnum.yes) {
            throw new ErrorDataException("结算单已完成结算");
        }
        if (account.getSettlementAmount().compareTo(amount) != 0) {
            throw new ErrorDataException("结算金额有误");
        }
        SettlementInfoVO result = new SettlementInfoVO(account);
        // 查询 是否存在随机码
        String key = fetchCacheKey(settlementSn);
        String randomCode = scmIncrementUtil.fetchValue(key);
        boolean flag = StrUtil.isEmpty(randomCode);
        if (flag) {
            randomCode = RandomUtil.randomStringUpper(6);
            // 有效期 30分钟
            int cacheSeconds = 1800;
            if (!scmIncrementUtil.setValue(key, randomCode, cacheSeconds)) {
                throw new ErrorDataException("准备结算时错误");
            }
        }
        result.setSettleFlag(flag);
        result.setRandomCode(randomCode);
        return result;
    }

    /**
     * 线上准备结算, 多账单合并后准备, 单个账单准备
     *
     * @param req 账单编号列表
     * @return
     */
    @Override
    public SettlementInfoVO readyOnlineSettlement(SummarySettlementReq req) {
        List<String> billSnList = req.getBillSnList();
        if (CollUtil.isEmpty(billSnList)) {
            throw new ErrorDataException("待结算账单有误");
        }
        SettlementAccount settlement;
        // 单账单处理
        if (billSnList.size() == 1) {
            settlement = settlementAccountService.fetchByBillSn(billSnList.get(0));
        } else {
            // 多账单合并
            settlement = createSummarySettlement(req);
        }
        return readySettlement(settlement.getSettlementSn(), settlement.getSettlementAmount());
    }

    /**
     * 完成结算记录
     *
     * @param settlement 结算必须参数
     *                   settlementSn
     *                   companyOperator
     *                   operatorName
     *                   randomCode
     *                   可选参数
     *                   payType
     *                   settlementTime
     *                   settlementDesc
     *                   photoList 结算图片列表
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void finishSettlement(SettlementDTO settlement) {
        String settlementSn = settlement.getSettlementSn();
        SettlementAccount exist = checkSettlement(settlementSn, settlement.getRandomCode());
        log.debug("====完成结算记录====exist:{}", exist);
        // 结算单类型处理
        switch (exist.getSettlementStyle()) {
            case normal:
                if (StrUtil.isEmpty(exist.getBillSn())) {
                    throw new ErrorDataException("结算单有误, 无对应账单:" + settlementSn);
                }
                // 账单结算状态修改
                log.debug("====账单结算状态修改====normal");
                BillServiceFactory.getBillService(exist.getSettlementType())
                        .finishBillSettlement(exist.getBillSn());
                break;
            case summary:
                List<SettlementAccount> detailList = settlementAccountService.fetchDetailOfSummarySn(settlementSn);
                if (detailList.isEmpty()) {
                    throw new ErrorDataException("明细结算单有误");
                }
                log.debug("====账单结算状态修改====summary");
                detailList.forEach(detail -> {
                    if (StrUtil.isEmpty(detail.getBillSn())) {
                        throw new ErrorDataException("结算单有误, 无对应账单:" + detail.getSettlementSn());
                    }
                    // 明细结算单状态修改
                    SettlementAccount update = new SettlementAccount();
                    update.setId(detail.getId());
                    update.setSettlementStatus(YesNoEnum.yes);
                    update.setPayType(exist.getPayType());
                    update.setSettlementTime(exist.getSettlementTime());
                    settlementAccountService.updateById(update);
                    // 账单结算状态修改
                    BillServiceFactory.getBillService(detail.getSettlementType())
                            .finishBillSettlement(detail.getBillSn());
                });
                break;
            default:
                throw new ErrorDataException("结算单类型有误, 无法结算");
        }
        finishSettlementUpdate(settlement, exist);
    }

    private SettlementAccount checkSettlement(String settlementSn, String randomCode) {
        SettlementAccount exist = settlementAccountService.fetchBySn(settlementSn);
        if (null == exist || null == exist.getSettlementStyle() || null == exist.getSettlementType()) {
            throw new ErrorDataException("结算单有误");
        }
        if (exist.getSettlementStatus() == YesNoEnum.yes) {
            throw new ErrorDataException("结算单已完成结算");
        }
        // 在线支付时 tenantId需设置
        if (null == TenantContextHolder.getTenantId()) {
            TenantContextHolder.setTenantId(exist.getTenantId());
            return exist;
        }
        // 随机码比对
        String code = queryRandomCode(settlementSn);
        if (null != code && !StrUtil.equals(code, randomCode)) {
            throw new ErrorDataException("结算有误, code有误");
        }
        return exist;
    }

    /**
     * 完成结算单的记录修改
     *
     * @param settlement
     * @param exist
     */
    private void finishSettlementUpdate(SettlementDTO settlement, SettlementAccount exist) {
        SettlementAccount update = ScmSettlementTrans.copyFinishProperty(settlement);
        update.setSettlementStatus(YesNoEnum.yes);
        update.setId(exist.getId());
        // 图片保存 结算图片
        if (CollUtil.isNotEmpty(settlement.getPhotoList())) {
            PhotoGroupDTO photoGroupDTO = PhotoTrans.initToEdit("结算凭证-".concat(exist.getCompanyName()),
                    exist.getGroupNum(), PhotoTypeEnum.billPhotos, settlement.getPhotoList());
            update.setGroupNum(scmBillUtils.savePhotoGroup(photoGroupDTO));
        }
        settlementAccountService.updateById(update);
    }

    /**
     * 结算单缓存 随机码 键值
     *
     * @param settlementSn
     * @return
     */
    private String fetchCacheKey(String settlementSn) {
        Integer tenantId = TenantContextHolder.getTenantId();
        String infix = (null != tenantId) ? StrUtil.COLON + tenantId.toString() + StrUtil.COLON : StrUtil.COLON;
        return ScmCacheNameConstant.SETTLEMENT_RANDOM_CODE + infix + settlementSn;
    }

    /**
     * 查询随机码
     *
     * @param settlementSn
     * @return null(未找到时)
     */
    private String queryRandomCode(String settlementSn) {
        return scmIncrementUtil.fetchValue(fetchCacheKey(settlementSn));
    }

    /**
     * 结算撤销 (结算单对应账单需重新审核, 结算单删除)
     *
     * @param settlementSn
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revokeSettlement(String settlementSn) {
        SettlementAccount exist = settlementAccountService.fetchBySn(settlementSn);
        if (null == exist || null == exist.getSettlementStyle()) {
            throw new ErrorDataException("结算单有误");
        }
        // 按 结算类型处理 （ 汇总， 明细，普通）
        switch (exist.getSettlementStyle()) {
            case detail:
                // 汇总结算单
                SettlementAccount summary = settlementAccountService.fetchBySn(exist.getSummarySettlementSn());
                if (null != summary) {
                    // 汇总单需未结算
                    if (summary.getSettlementStatus() == YesNoEnum.yes) {
                        throw new ErrorDataException("汇总结算单已结算");
                    }
                    // 汇总单价格更新
                    BigDecimal amount;
                    // 汇总单和明细单的结算类型一致
                    if (summary.getSettlementType() == exist.getSettlementType()) {
                        amount = summary.getSettlementAmount().subtract(exist.getSettlementAmount());
                    } else {
                        amount = summary.getSettlementAmount().add(exist.getSettlementAmount());
                    }
                    if (amount.compareTo(BigDecimal.ZERO) > 0) {
                        SettlementAccount update = new SettlementAccount();
                        update.setId(summary.getId());
                        update.setSettlementAmount(amount);
                        settlementAccountService.updateById(update);
                    } else {
                        // 当前明细金额大于等于汇总单金额时不可处理
                        throw new ErrorDataException("明细结算单金额超过汇总结算单");
                    }
                }
                break;
            case summary:
                boolean flag = exist.getSettlementStatus() == YesNoEnum.yes;
                // 所有明细查询
                List<SettlementAccount> detailList = settlementAccountService.fetchDetailOfSummarySn(settlementSn);
                detailList.forEach(detail -> {
                    settlementAccountService.removeById(detail.getId());
                    BillServiceFactory.getBillService(detail.getSettlementType()).backBill(detail.getBillSn());
                });
                break;
            case normal:
                break;
            default:
                throw new ErrorDataException("未定义结算类型");
        }
        // 删除结算单
        settlementAccountService.removeById(exist.getId());
        // 账单重置
        String billSn = exist.getBillSn();
        if (StrUtil.isNotEmpty(billSn)) {
            BillServiceFactory.getBillService(exist.getSettlementType()).backBill(billSn);
        }
    }

    /**
     * 在线支付后完成结算
     *
     * @param settlement 结算必须参数
     *                   settlementSn
     *                   companyOperator
     *                   settlementTime
     */
    @Override
    public void payToSettlement(SettlementDTO settlement) {
        // 在线支付 tenantId置空处理
        TenantContextHolder.setTenantId(null);
        settlement.setOperatorName("在线支付");
        // 不验证随机码
        settlement.setRandomCode(null);
        settlement.setPayType(PayTypeEnum.online);
        settlement.setSettlementDesc(StrUtil.format("{} 完成在线支付.", settlement.getCompanyOperator()));
        // 完成结算时  根据结算单设置tenantId
        log.debug("====在线支付后完成结算====:{}", settlement);
        finishSettlement(settlement);
    }

    /**
     * 查询订单的支付状态
     *
     * @param orderSn
     * @return
     */
    @Override
    public PayStatusEnum queryOrderPayStatus(String orderSn) {
        List<SettlementAccount> list = settlementAccountService.findListByOrderSn(orderSn);
        if (list.isEmpty()) {
            return PayStatusEnum.wait;
        }
        // 已结算数
        int num = 0;
        for (SettlementAccount settlement : list) {
            if (settlement.getSettlementStatus() == YesNoEnum.yes) {
                num++;
            }
        }
        if (num == 0) {
            return PayStatusEnum.wait;
        }
        if (list.size() == num) {
            return PayStatusEnum.success;
        }
        return PayStatusEnum.part;
    }

    @Override
    public boolean updateMerchantName(String companyId, String companyName) {
        if (StrUtil.isEmpty(companyId) || StrUtil.isEmpty(companyName)) {
            throw new ErrorDataException("缺少参数companyId、companyName");
        }
        // 账单 pmName merchantName
        BillServiceFactory.getBillService(SettlementTypeEnum.receivable).updateMerchantName(companyId, companyName);
        BillServiceFactory.getBillService(SettlementTypeEnum.payable).updateMerchantName(companyId, companyName);
        //  结算单 pmName companyName
        SettlementAccount update = new SettlementAccount();
        update.setCompanyName(companyName);
        boolean flag = settlementAccountService.update(update, Wrappers.<SettlementAccount>query().lambda()
                .eq(SettlementAccount::getCompanyId, companyId)
        );
        update.setCompanyName(null);
        update.setPmName(companyName);
        return settlementAccountService.update(update, Wrappers.<SettlementAccount>query().lambda()
                .eq(SettlementAccount::getPmId, companyId)
        ) || flag;
    }
}
